//request 请求消息
// GET /XXX HTTP/1.1  根据请求的不同而有所区别
// Host: bigquant.com
// Connection: keep-alive
// Upgrade-Insecure-Requests: 1
// User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36
// Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
// Accept-Encoding: gzip, deflate, br
// Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
// \r\n

//response 回复消息
// HTTP/1.1 200 OK  (200 表示状态码，具体的状态码的意义见下方)
// Server: nginx/1.13.12
// Date: Fri, 15 Feb 2019 09:57:21 GMT
// Content-Type: text/html; charset=utf-8  （必须要填写）
// Transfer-Encoding: chunked
// Connection: keep-alive
// Vary: Accept-Encoding
// Vary: Cookie
// X-Frame-Options: SAMEORIGIN
// Set-Cookie: __bqusername=""; Domain=.bigquant.com; expires=Thu, 01-Jan-1970 00:00:00 GMT; Max-Age=0; Path=/
// Access-Control-Allow-Origin: *
// Content-Encoding: gzip
// \r\n

// 200：请求被正常处理 
// 204：请求被受理但没有资源可以返回 
// 206：客户端只是请求资源的一部分，服务器只对请求的部分资源执行GET方法，相应报文中通过Content-Range指定范围的资源。

// 301：永久性重定向 
// 302：临时重定向 
// 303：与302状态码有相似功能，只是它希望客户端在请求一个URI的时候，能通过GET方法重定向到另一个URI上 
// 304：发送附带条件的请求时，条件不满足时返回，与重定向无关 
// 307：临时重定向，与302类似，只是强制要求使用POST方法
 
// 400：请求报文语法有误，服务器无法识别
// 401：请求需要认证 
// 403：请求的对应资源禁止被访问 
// 404：服务器无法找到对应资源 

// 500：服务器内部错误 
// 503：服务器正忙

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <dirent.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include "HttpSession.h"

HttpSession::HttpSession()
{

}

HttpSession::~HttpSession()
{

}

void HttpSession::PraseHttpRequest(std::string &s)//解析客户端传来的请求报文 并将解析结果存储到结构体httprequestcontext_中
{
    //std::cout << "HttpServer::HttpParser" << std::endl;
    std::string msg;
    msg.swap(s);
	std::string crlf("\r\n"), crlfcrlf("\r\n\r\n");
	size_t prev = 0, next = 0, pos_colon;
	std::string key, value;

    //以下解析可以改成状态机，解决一次收Http报文不完整问题
	//prase http request line
	if ((next = msg.find(crlf, prev)) != std::string::npos)//查找换行符
	{
		std::string first_line(msg.substr(prev, next - prev));
		prev = next;
		std::stringstream sstream(first_line);// GET /XXX HTTP/1.1  
		sstream >> (httprequestcontext_.method);//按空格提取字符串
		sstream >> (httprequestcontext_.url);
		sstream >> (httprequestcontext_.version);
	}
	else
	{
		std::cout << "Error in httpPraser: http_request_line isn't complete!" << std::endl;
        //可以临时存起来，凑齐了再解析
	}
    //prase http request header
	size_t pos_crlfcrlf = 0;
	if (( pos_crlfcrlf = msg.find(crlfcrlf, prev)) != std::string::npos)// \r\n\r\n代表到达了请求头的结尾
	{
		while (prev != pos_crlfcrlf)
        {
            next = msg.find(crlf, prev + 2);//从下一行的开头位置开始寻找
            pos_colon = msg.find(":", prev + 2);
            key = msg.substr(prev + 2, pos_colon - prev-2);
            value = msg.substr(pos_colon + 2, next-pos_colon-2);
            prev = next;
            httprequestcontext_.header.insert(std::pair<std::string, std::string>(key, value));
        }
	}
    else
    {
        std::cout << "Error in httpPraser: http_request_header isn't complete!" << std::endl;
    }	
    //prase http request body
	httprequestcontext_.body = msg.substr(pos_crlfcrlf + 4);
}

void HttpSession::HttpProcess()//根据客户端发来的请求 编写回复消息
{
    if("GET" == httprequestcontext_.method)
    {
        ;
    }
    else if("POST" == httprequestcontext_.method)
    {
        ;
    }
    else//对于除了GET和POST之外请求 不进行处理
    {
        std::cout << "HttpServer::HttpParser" << std::endl;
        errormsg = "Method Not Implemented";
        HttpError(501, "Method Not Implemented");
    }

    size_t pos = httprequestcontext_.url.find("?");
    if(pos != std::string::npos)
    {
        path_ = httprequestcontext_.url.substr(0, pos);
        querystring_ = httprequestcontext_.url.substr(pos+1);
    }
    else
    {
        path_ = httprequestcontext_.url;
    }
    
    //keepalive判断处理  Connection: keep-alive  ; Connection: close
    std::map<std::string, std::string>::const_iterator iter = httprequestcontext_.header.find("Connection");
    if(iter != httprequestcontext_.header.end())
    {
        keepalive_ = (iter->second == "Keep-Alive");
    }
    else//缺省情况下
    {
        if(httprequestcontext_.version == "HTTP/1.1")
        {
            keepalive_ = true;//HTTP/1.1默认长连接 Connection: keep-alive
        }
        else
        {
            keepalive_ = false;//HTTP/1.0默认短连接 Connection: close
        }             
    }  


    if("/hello" == path_)
    {
        //Wenbbench 测试用
        responsebody_ = ("hello world");
        responsecontext_ += httprequestcontext_.version + " 200 OK\r\n";
        responsecontext_ += "Server: My NetServer/0.1\r\n";
        responsecontext_ += "Content-Type: text/plain; charset=utf-8\r\n";
        responsecontext_ += "Connection: close\r\n";
        responsecontext_ += "Content-Length: -1\r\n";
        responsecontext_ += "\r\n";
        responsecontext_ += responsebody_;
        return;
    } 

    path_ = path_.substr(1);//去除路径上的‘/’
    struct stat st;
    if(path_ == "") path_ = "./";
    int ret = stat(path_.c_str(), &st);//根目录是当前进程所在目录
    if (ret == -1)
    {
        HttpError(404, "NO such file or direntry");
        return;
    }
    // 判断是目录还是文件
    if (S_ISDIR(st.st_mode))
    { // 目录
        send_dir(path_.c_str());
    }
    else if (S_ISREG(st.st_mode))
    { // 文件
        send_file(path_.c_str());
    }      
}

void HttpSession::AddToBuf(std::string &s)
{
    responsecontext_.swap(s);
}

//HttpError(501, "Method Not Implemented");
void HttpSession::HttpError(int err_num, std::string short_msg)
{
    //这里string创建销毁应该会耗时间
    //std::string body_buff;
    responsebody_ += "<html><title>出错了</title>";
    responsebody_ += "<head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"></head>";
    responsebody_ += "<style>body{background-color:#f;font-size:14px;}h1{font-size:60px;color:#eeetext-align:center;padding-top:30px;font-weight:normal;}</style>";
    responsebody_ += "<body bgcolor=\"ffffff\"><h1>";
    responsebody_ += std::to_string(err_num) + " " + short_msg;
    responsebody_ += "</h1><hr><em> My NetServer</em>\n</body></html>";

    responsecontext_ += httprequestcontext_.version + " " + std::to_string(err_num) + " " + short_msg + "\r\n";
    responsecontext_ += "Server: Chen Shuaihao's NetServer/0.1\r\n";
    responsecontext_ += "Content-Type: text/html\r\n";
    responsecontext_ += "Connection: close\r\n";
    responsecontext_ += "Content-Length: -1 \r\n";
    responsecontext_ += "\r\n";
    responsecontext_ += responsebody_;
}

// 发送目录内容
void HttpSession::send_dir(const char *dirname)
{

    // 拼一个html页面<table></table>
    char buf[4094] = {0};

    sprintf(buf, "<html><head><title>目录名: %s</title></head>", dirname);
    sprintf(buf + strlen(buf), "<body><h1>当前目录: %s</h1><table>", dirname);

    char enstr[1024] = {0};
    char path[1024] = {0};

    // 目录项二级指针
    struct dirent **ptr;
    int num = scandir(dirname, &ptr, NULL, alphasort);
    //遍历目录dirname 将目录下的文件存到ptr数组中 alphasort表示按首字母排序

    // 遍历
    for (int i = 0; i < num; ++i)
    {

        char *name = ptr[i]->d_name;

        // 拼接文件的完整路径
        sprintf(path, "%s/%s", dirname, name);
        printf("path = %s ===================\n", path);
        struct stat st;
        stat(path, &st);

        // 编码生成 %E5 %A7 之类的东西
        encode_str(enstr, sizeof(enstr), name);

        // 如果是文件
        if (S_ISREG(st.st_mode))
        {
            sprintf(buf + strlen(buf),
                    "<tr><td><a href=\"%s\">%s</a></td><td>%ld</td></tr>",
                    enstr, name, (long)st.st_size);
        }
        else if (S_ISDIR(st.st_mode))
        { // 如果是目录
            sprintf(buf + strlen(buf),
                    "<tr><td><a href=\"%s/\">%s/</a></td><td>%ld</td></tr>",
                    enstr, name, (long)st.st_size);
        }
        responsebody_.append(buf);
        memset(buf, 0, sizeof(buf));
        // 字符串拼接
    }

    sprintf(buf + strlen(buf), "</table></body></html>");
    responsebody_.append(buf);

    responsecontext_ += httprequestcontext_.version + " 200 OK\r\n";
    responsecontext_ += "Server: My NetServer/0.1\r\n";
    responsecontext_ += "Content-Type: text/html; charset=utf-8\r\n";
    // Content-Type: text/html; charset=utf-8 
    responsecontext_ += "Connection: close \r\n";
    responsecontext_ += "Content-Length: -1\r\n";
    responsecontext_ += "\r\n";
    responsecontext_ += responsebody_; 

    printf("dir message send OK!\n");
}



// 发送文件
void HttpSession::send_file(const char *filename)
{
    // 打开文件
    int fd = open(filename, O_RDONLY);
    if (fd == -1)
    {
        HttpError(404, "NO such file or direntry");
        exit(1);
    }

    // 循环读文件
    char buf[4096] = {0};
    int len = 0;
    while ((len = read(fd, buf, sizeof(buf))) > 0)
    {
        // 发送读出的数据 
        responsebody_.append(buf);    
    }
    if (len == -1)
    {
        perror("read file error");
        exit(1);
    }
    close(fd);

    responsecontext_ += httprequestcontext_.version + " 200 OK\r\n";
    responsecontext_ += "Server: My NetServer/0.1\r\n";
    responsecontext_ += "Content-Type: text/plain; charset=utf-8\r\n";
    responsecontext_ += "Connection: close \r\n";
    responsecontext_ += "Content-Length: -1 \r\n";
    responsecontext_ += "\r\n";
    responsecontext_ += responsebody_; 
    
    printf("file message send OK!\n");
}

// 16进制数转化为10进制
int HttpSession::hexit(char c)
{
    if (c >= '0' && c <= '9')
        return c - '0';
    if (c >= 'a' && c <= 'f')
        return c - 'a' + 10;
    if (c >= 'A' && c <= 'F')
        return c - 'A' + 10;

    return 0;
}

/*
 *  这里的内容是处理%20之类的东西！是"解码"过程。
 *  %20 URL编码中的‘ ’(space)
 *  %21 '!' %22 '"' %23 '#' %24 '$'
 *  %25 '%' %26 '&' %27 ''' %28 '('......
 *  相关知识html中的‘ ’(space)是&nbsp
 */
void HttpSession::encode_str(char *to, int tosize, const char *from)
{
    int tolen;

    for (tolen = 0; *from != '\0' && tolen + 4 < tosize; ++from)
    {
        if (isalnum(*from) || strchr("/_.-~", *from) != (char *)0)
        {
            *to = *from;
            ++to;
            ++tolen;
        }
        else
        {
            sprintf(to, "%%%02x", (int)*from & 0xff);
            to += 3;
            tolen += 3;
        }
    }
    *to = '\0';
}